//GA omp
//$Id: ga_omp.h 696 2011-04-22 12:21:01Z Oleg.Bulychov $
#pragma once

#include "src/heo/include/easy_omp.h"
#include "src/heo/include/ring_coop.h"
#include "src/heo/include/xml_file.h"
#include "ga_common.h"

//__________________________________________________________________________________
/**
The Island Model with Ring Migration
*/
template<class T>
class GA_OMP:
    public GA<T>
{
public:
    typedef typename GA<T>::TEnvironment TEnvironment;
    typedef typename GA<T>::TSolution TSolution;

    typedef typename GA<T>::TDirection TDirection;
    typedef typename GA<T>::TCostType  TCostType;

protected:
    //TODO move to template arg?
    ring_cooperation<omp::recv, omp::send, omp::isend> coop_;
    bool omp_island_mode_;

    struct Shared
    {
        volatile int stop_;
    };

public:
    template<class TSolutionPool, class TStatistics>
    void solve(TSolutionPool& pool, TStatistics& stat)
    {
        int np = omp_get_max_threads();
        if (1 == np)
        {//:don't bother omp
            GA<T>::solve(pool, stat);
            return;
        }
        if (omp_island_mode_)
        {
            Shared shared = {};
            omp::solver_fork< TSolutionPool, TStatistics, GA_OMP<T> > f(pool, stat, *this);
            omp::communicator::run(np, f, shared);
        }
        else
        {
            solve_OMP_Ex(pool, stat);
        }
    }

    template<class V>
    void accept(V& v)
    {
        v(static_cast<GA<T>&>(*this), "/");
        v(VAR_(coop));
        v(VAR_(omp_island_mode), false);
    }

    template<class TInOutSolution, class TStatistics>
    void solve_OMP(Solution_Pool<TInOutSolution>& pool,
        TStatistics& stat,
        omp::communicator& comm,
        Shared& shared)
    {
        ASSERT_(this->rank_ == comm.rank());
        int np   = comm.size();
        int prev = (np + this->rank_ - 1) % np;
        int next = (this->rank_ + 1)      % np;

        this->init_From_Pool(pool);
        if (this->coop_.is_Async_Mode())
            this->init_Async_Buffer();
        this->evaluate_Parents();
        this->update_Elite(this->parent_, this->population_size_);
        this->update_Best_Solution();
        this->calc_Max_Step();
        this->show_Progress(0);
        stat.update(0, this->rank_, *this, this->env_);

        for (int step = 1; step <= this->max_step_; ++step)
        {
            this->select_Parents();

            this->apply_Intra_Operators(step);

            this->evaluate_Offsprings();
            this->update_Elite(this->offspring_, this->offspring_size_);

            this->select_Offsprings();

            this->update_Best_Solution();

            //this->dump_Anytime_Solution(step);

            this->show_Progress(step);

            stat.update(step, this->rank_, *this, this->env_);

            if (this->stop(step))
            {
                if (1 == np || !this->coop_.get_Rate())
                    break;//:no coop - no need for 'pending stop'
                this->pending_stop_ = 1;
                shared.stop_ = 1;
            }

            if (this->coop_.cooperate(*this, this->rank_, prev, next, np, step, comm))
            {
                if (this->coop_.is_Async_Mode())
                {
                    if (this->sent_stop_)
                        break;
                }
                else
                {
                    if (shared.stop_)
                        break;
                }
            }
        }

        //:this->evaluate_Parents();
        //this->sort_Population();

        this->clear_OMP(comm);

        //this->dump_Anytime_Solution(-1);

        this->update_Pool_OMP(pool, comm, np);
    }

private:
    //void dump_Anytime_Solution(int nStep)//1-based
    //{
    //    if (!env_->anytime_rate_ || !env_->output_dir_.)
    //        return;

    //    if (nStep <= 0 || (nStep % env_->anytime_rate_ == 0))
    //    {
    //        std::cout << "** " << nStep << std::endl;
    //    }
    //}

    template<class TInOutSolution>
    void update_Pool_OMP(Solution_Pool<TInOutSolution>& pool, omp::communicator& comm, int np)
    {
        if (0 == this->rank_)
        {
            pool.sol_list_.resize(np);
        }
        comm.barrier();

        TSolution const& final_sol = this->get_Final_Solution();
        //:final_sol --> pool

        TInOutSolution& it = pool.sol_list_[this->rank_];
        it.init(this->env_, this->rnd_);
        it.assign(final_sol);

        if (this->get_Debug_Level())
        {
#pragma omp critical(debug_output)
            {
                std::cout << lf 
                          << this->rank_ << ": final fitness = " << final_sol.get_Fitness() << lf;
            }
        }

        comm.barrier();
    }

    void clear_OMP(omp::communicator& comm)
    {
        comm.barrier();
        if (!coop_.is_Async_Mode())
            return;
        comm.clear_Data();
        comm.barrier();
    }

//=================================================================================
//=================================================================================
//=================================================================================

    template<class TInOutSolution, class TStatistics>
    void solve_OMP_Ex(Solution_Pool<TInOutSolution>& pool,
        TStatistics& stat)
    {
        this->init_From_Pool(pool);
        this->evaluate_Parents_OMP();
        this->update_Elite(this->parent_, this->population_size_);
        this->update_Best_Solution();
        this->calc_Max_Step();
        this->show_Progress(0);
        stat.update(0, this->rank_, *this, this->env_);

        for (int step = 1; step <= this->max_step_; ++step)
        {
            this->select_Parents();
            this->apply_Intra_Operators_OMP(step);
            this->evaluate_Offsprings_OMP();
            this->update_Elite(this->offspring_, this->offspring_size_);
            this->select_Offsprings();
            this->update_Best_Solution();

            this->show_Progress(step);

            stat.update(step, this->rank_, *this, this->env_);

            if (this->stop(step))
            {
                break;
            }
        }
        this->update_Pool(pool);
    }

    void evaluate_Parents_OMP()
    {
#pragma omp parallel
        {
            int rank = omp_get_thread_num();
            int np = omp_get_num_threads();
            random_generator rnd(rank);
            for (int i = rank; i < this->population_size_; i += np)
            {
                this->parent_[i]->evaluate(this->env_, rnd);
            }
        }
    }

    void apply_Intra_Operators_OMP(int step)
    {
        for (size_t i = 0; i < this->operator_list_.size(); ++i)
        {
            this->operator_list_[i]->apply_OMP(this->offspring_, this->offspring_size_,
                this->env_, this->rank_,
                step, this->max_step_);
        }
    }

    void evaluate_Offsprings_OMP()
    {
#pragma omp parallel
        {
            int rank = omp_get_thread_num();
            int np = omp_get_num_threads();
            random_generator rnd(rank);
            for (int i = rank; i < this->offspring_size_; i += np)
            {
                this->offspring_[i]->evaluate(this->env_, rnd);
            }
        }
    }
};
//__________________________________________________________________________________
//__________________________________________________________________________________

//EOF!
